<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Itjqd" id="Itjqd"><span data-lake-id="ud630f6b9" id="ud630f6b9">典型回答</span></h1>
  <p data-lake-id="ue9f73688" id="ue9f73688"><br></p>
  <p data-lake-id="u1836da8f" id="u1836da8f"><span data-lake-id="u860a94f4" id="u860a94f4">RocketMQ的事务消息是通过TransactionListener接口来实现的。</span></p>
  <p data-lake-id="u85f82ae3" id="u85f82ae3"><span data-lake-id="uf06891be" id="uf06891be">​</span><br></p>
  <p data-lake-id="ua65a5f16" id="ua65a5f16"><span data-lake-id="u5cd32f73" id="u5cd32f73">在发送事务消息时，首先向RocketMQ Broker发送一条“half消息”（即半消息），半消息将被存储在Broker端的事务消息日志中，但是这个消息还不能被消费者消费。</span></p>
  <p data-lake-id="ue3cafe67" id="ue3cafe67"><span data-lake-id="u7da88c3c" id="u7da88c3c">​</span><br></p>
  <p data-lake-id="u32c9a577" id="u32c9a577"><span data-lake-id="u435b3b31" id="u435b3b31">接下来，在半消息发送成功后，应用程序通过执行本地事务来确定是否要提交该事务消息。</span></p>
  <p data-lake-id="u86ab4251" id="u86ab4251"><span data-lake-id="u722a2506" id="u722a2506">​</span><br></p>
  <p data-lake-id="u536a90b7" id="u536a90b7"><span data-lake-id="uab63ef32" id="uab63ef32">如果本地事务执行成功，就会通知RocketMQ Broker提交该事务消息，使得该消息可以被消费者消费；否则，就会通知RocketMQ Broker回滚该事务消息，该消息将被删除，从而保证消息不会被消费者消费。</span></p>
  <p data-lake-id="u291c3c77" id="u291c3c77"><span data-lake-id="u3ee157ec" id="u3ee157ec">​</span><br></p>
  <p data-lake-id="u552f297b" id="u552f297b"><img src="https://cdn.nlark.com/yuque/0/2023/jpeg/5378072/1676883263316-1e97eaa2-df90-44f6-8cc9-3037a438d0a0.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_33%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="ufb02b904" id="ufb02b904"><span data-lake-id="u5ae53d59" id="u5ae53d59">​</span><br></p>
  <p data-lake-id="u5b2c1f6f" id="u5b2c1f6f"><span data-lake-id="uc9579421" id="uc9579421">拆解下来的话，主要有以下4个步骤：</span></p>
  <p data-lake-id="ud8d5b47b" id="ud8d5b47b"><br></p>
  <ol list="uee2dfa8d">
   <li fid="ufa389bf0" data-lake-id="u59f539b5" id="u59f539b5"><span data-lake-id="u05bff483" id="u05bff483">发送半消息：应用程序向RocketMQ Broker发送一条半消息，该消息在Broker端的事务消息日志中被标记为“prepared”状态。</span></li>
  </ol>
  <p data-lake-id="u21ef987f" id="u21ef987f"><br></p>
  <ol list="uee2dfa8d" start="2">
   <li fid="ufa389bf0" data-lake-id="u2f01c4e3" id="u2f01c4e3"><span data-lake-id="u2292b5b7" id="u2292b5b7">执行本地事务：RocketMQ会通知应用程序执行本地事务。如果本地事务执行成功，应用程序通知RocketMQ Broker提交该事务消息。</span></li>
  </ol>
  <p data-lake-id="u0ecf636e" id="u0ecf636e"><br></p>
  <ol list="uee2dfa8d" start="3">
   <li fid="ufa389bf0" data-lake-id="u7c6a6d61" id="u7c6a6d61"><span data-lake-id="ua486b1f4" id="ua486b1f4">提交事务消息：RocketMQ收到提交消息以后，会将该消息的状态从“prepared”改为“committed”，并使该消息可以被消费者消费。</span></li>
  </ol>
  <p data-lake-id="ud1838e5c" id="ud1838e5c"><br></p>
  <ol list="uee2dfa8d" start="4">
   <li fid="ufa389bf0" data-lake-id="u24ddeed1" id="u24ddeed1"><span data-lake-id="u69c1e539" id="u69c1e539">回滚事务消息：如果本地事务执行失败，应用程序通知RocketMQ Broker回滚该事务消息，RocketMQ将该消息的状态从“prepared”改为“rollback”，并将该消息从事务消息日志中删除，从而保证该消息不会被消费者消费。</span></li>
  </ol>
  <p data-lake-id="udd4130ca" id="udd4130ca"><span data-lake-id="u1ec83069" id="u1ec83069">​</span><br></p>
  <h1 data-lake-id="UegkI" id="UegkI"><span data-lake-id="u568596f1" id="u568596f1">扩展知识</span></h1>
  <p data-lake-id="u07f49e34" id="u07f49e34"><br></p>
  <h2 data-lake-id="BUMfi" id="BUMfi"><span data-lake-id="u5adc3d95" id="u5adc3d95" class="lake-fontsize-12" style="color: rgb(52, 53, 65)">如果一直没收到COMMIT或者ROLLBACK怎么办？</span></h2>
  <p data-lake-id="ub1bff61e" id="ub1bff61e"><br></p>
  <p data-lake-id="u6e348177" id="u6e348177"><span data-lake-id="uc3fc3756" id="uc3fc3756">在RocketMQ的事务消息中，如果半消息发送成功后，RocketMQ Broker在规定时间内没有收到</span><span data-lake-id="u6627a885" id="u6627a885" class="lake-fontsize-12" style="color: rgb(52, 53, 65)">COMMIT或者ROLLBACK</span><span data-lake-id="ue6e429bb" id="ue6e429bb">消息。</span></p>
  <p data-lake-id="ue252bf20" id="ue252bf20"><span data-lake-id="ucc4c5425" id="ucc4c5425">​</span><br></p>
  <p data-lake-id="ua75dc14e" id="ua75dc14e"><span data-lake-id="ue08c561c" id="ue08c561c">RocketMQ会向应用程序发送一条检查请求，应用程序可以通过回调方法返回是否要提交或回滚该事务消息。如果应用程序在规定时间内未能返回响应，RocketMQ会将该消息标记为“UNKNOW”状态。</span></p>
  <p data-lake-id="ude21a55e" id="ude21a55e"><span data-lake-id="u42b0564d" id="u42b0564d">​</span><br></p>
  <p data-lake-id="u078d6f5c" id="u078d6f5c"><span data-lake-id="uc3a4845b" id="uc3a4845b">在标记为“UNKNOW”状态的事务消息中，如果应用程序有了明确的结果，还可以向MQ发送COMMIT或者ROLLBACK。</span></p>
  <p data-lake-id="ufcbf62a5" id="ufcbf62a5"><span data-lake-id="u7f5cee94" id="u7f5cee94">​</span><br></p>
  <p data-lake-id="u57837779" id="u57837779"><span data-lake-id="uc77c824f" id="uc77c824f">但是MQ不会一直等下去，如果过期时间已到，RocketMQ会自动回滚该事务消息，将其从事务消息日志中删除。</span></p>
  <p data-lake-id="uc6444da7" id="uc6444da7"><span data-lake-id="u424696b4" id="u424696b4">​</span><br></p>
  <p data-lake-id="u1bbaf251" id="u1bbaf251"><span data-lake-id="u51dfbcb6" id="u51dfbcb6">​</span><br></p>
  <h2 data-lake-id="oJ50q" id="oJ50q"><span data-lake-id="uab237399" id="uab237399">第一次发送半消息失败了怎么办？</span></h2>
  <p data-lake-id="ufd0baa21" id="ufd0baa21"><br></p>
  <p data-lake-id="u2b77b602" id="u2b77b602"><span data-lake-id="u97864719" id="u97864719">在事务消息的一致性方案中，我们是先发半消息，再做业务操作的</span></p>
  <p data-lake-id="u42b314ee" id="u42b314ee"><span data-lake-id="ue22fe180" id="ue22fe180">​</span><br></p>
  <p data-lake-id="udf2150a9" id="udf2150a9"><span data-lake-id="u4a7c0331" id="u4a7c0331">所以，如果半消息发失败了，那么业务操作也不会进行，不会有不一致的问题。</span></p>
  <p data-lake-id="u582ab35e" id="u582ab35e"><span data-lake-id="u3cafdcf5" id="u3cafdcf5">​</span><br></p>
  <p data-lake-id="u37b21315" id="u37b21315"><span data-lake-id="u9e72ad89" id="u9e72ad89">遇到这种情况重试就行了。（可以自己重试，也可以依赖上游重试）</span></p>
  <p data-lake-id="u2d4304bf" id="u2d4304bf"><span data-lake-id="ucbc6299c" id="ucbc6299c">​</span><br></p>
  <h2 data-lake-id="FO6BU" id="FO6BU"><span data-lake-id="uaf28d6f4" id="uaf28d6f4">为什么要用事务消息？</span></h2>
  <p data-lake-id="ufe120a80" id="ufe120a80"><br></p>
  <p data-lake-id="ua4d375c5" id="ua4d375c5"><span data-lake-id="uaa665cdf" id="uaa665cdf">很多人看完事务消息会有一个疑惑：本地事务执行完成之后再发送消息有什么区别？为什么要有事务消息呢？</span></p>
  <p data-lake-id="ubcfd5c54" id="ubcfd5c54"><span data-lake-id="udb853b25" id="udb853b25">​</span><br></p>
  <p data-lake-id="u27e67e5d" id="u27e67e5d"><span data-lake-id="u4ac7189e" id="u4ac7189e">主要是因为：本地事务执行完成之后再发送消息可能会发消息失败。</span></p>
  <p data-lake-id="u4b403ef9" id="u4b403ef9"><span data-lake-id="u6fac5e18" id="u6fac5e18">​</span><br></p>
  <p data-lake-id="u46321b36" id="u46321b36"><span data-lake-id="u1415e5c9" id="u1415e5c9">一旦发送消息失败了，那么本地事务提交了，但是消息没成功，那么监听者就收不到消息，那么就产生数据不一致了。</span></p>
  <p data-lake-id="uc215178c" id="uc215178c"><span data-lake-id="u67d82b40" id="u67d82b40">​</span><br></p>
  <p data-lake-id="u9db86e4c" id="u9db86e4c"><span data-lake-id="u1810e386" id="u1810e386">那如果用事务消息。先提交一个半消息，然后执行本地事务，再发送一个commit的半消息。如果后面这个commit半消息失败了，MQ是可以基于第一个半消息不断反查来推进状态的。这样只要本地事务提交成功，最终MQ也会成功。如果本地事务rolllback，那么MQ的消息也会rollback。保证了一致性。</span></p>
 </body>
</html>